package com.tianji.promotion.service.impl;

import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.autoconfigure.redisson.annotations.Lock;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.NumberUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.constants.PromotionConstants;
import com.tianji.promotion.domain.dto.UserCouponDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.UserCouponQuery;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.enums.ExchangeCodeStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.ExchangeCodeService;
import com.tianji.promotion.service.UserCouponService;
import com.tianji.promotion.utils.CodeUtil;
import com.tianji.promotion.utils.MyLock;
import com.tianji.promotion.utils.MyLockStrategy;
import com.tianji.promotion.utils.MyLockType;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.tianji.promotion.constants.PromotionConstants.COUPON_CODE_MAP_KEY;
import static com.tianji.promotion.constants.PromotionConstants.COUPON_RANGE_KEY;


/**
 * @author Cammy
 * @description 针对表【user_coupon(用户领取优惠券的记录，是真正使用的优惠券信息)】的数据库操作Service实现
 * @createDate 2025-02-25 09:21:24
 */
@Service
@RequiredArgsConstructor
public class UserCouponServiceImpl extends ServiceImpl<UserCouponMapper, UserCoupon>
        implements UserCouponService {

    private final CouponMapper couponMapper;

    private final ExchangeCodeService exchangeCodeService;

    private final StringRedisTemplate redisTemplate;

    private RabbitMqHelper mqHelper;

    private static final RedisScript<Long> RECEIVE_COUPON_SCRIPT;
    private static final RedisScript<String> EXCHANGE_COUPON_SCRIPT;

    static {
        RECEIVE_COUPON_SCRIPT = RedisScript.of(new ClassPathResource("lua/receive_coupon.lua"), Long.class);
        EXCHANGE_COUPON_SCRIPT = RedisScript.of(new ClassPathResource("lua/exchange_coupon.lua"), String.class);
    }

    /**
     * 领取优惠券接口
     * @param couponId
     */
    @Override
//    @Transactional
//    @Lock(name = "lock:coupon:#{couponId}")
    public void receiveCoupon(Long couponId) {
//        // 1.查询优惠券
//        Coupon coupon = queryCouponByCache(couponId);
//        if (ObjectUtils.isEmpty(coupon)) {
//            throw new BadRequestException("优惠券不存在");
//        }
//        // 2.校验发放时间
//        LocalDateTime now = LocalDateTime.now();
//        if (now.isBefore(coupon.getIssueBeginTime()) || now.isAfter(coupon.getIssueEndTime())) {
//            throw new BadRequestException("优惠券发放已经结束或尚未开始");
//        }
//        // 3.校验库存
//        if (coupon.getTotalNum() <= coupon.getIssueNum()) {
//            throw new BadRequestException("优惠券库存不足");
//        }

        // 1.执行LUA脚本，判断结果
        String key1 = PromotionConstants.COUPON_CACHE_KEY_PREFIX + couponId;
        String key2 = PromotionConstants.USER_COUPON_CACHE_KEY_PREFIX + couponId;
        Long userId = UserContext.getUser();

        // 1.2 执行脚本
        Long execute = redisTemplate.execute(RECEIVE_COUPON_SCRIPT, List.of(key1, key2), userId.toString());
        int result = NumberUtils.null2Zero(execute).intValue();
        if (result != 0) {
            // 结果大于0.说明出现异常
            throw new BizIllegalException(PromotionConstants.RECEIVE_COUPON_ERROR_MSG[result - 1]);
        }

//        // 4.校验每人限领数量
//        // 4.1.统计当前用户对当前优惠券的已经领取的数量
//        Integer count = lambdaQuery()
//                .eq(UserCoupon::getUserId, userId)
//                .eq(UserCoupon::getCouponId, couponId)
//                .count();
//        // 4.2.校验限领数量
//        if (count != null && count >= coupon.getUserLimit()) {
//            throw new BadRequestException("超出领取数量");
//        }
//        // 5.更新优惠券的已经发放的数量 + 1
//        couponMapper.incrIssueNum(coupon.getId());
//        // 6.新增一个用户券
//        saveUserCoupon(coupon, userId);
        // 4.校验并生成用户券
        // 这里加锁，这样锁在事务之外
//        synchronized (userId.toString().intern()) {
        // 4.1 校验没人领取数量
//        String key = PromotionConstants.USER_COUPON_CACHE_KEY_PREFIX + couponId;
//        Long value = redisTemplate.opsForHash().increment(key, userId.toString(), 1);
//        // 4.2 校验领取数量
//        UserCouponService userCouponService = (UserCouponService) AopContext.currentProxy();
//        userCouponService.checkAndCreateUserCoupon(coupon, userId, null);
//        // 5.扣减优惠券库存
//        redisTemplate.opsForHash().increment(
//                PromotionConstants.COUPON_CACHE_KEY_PREFIX + couponId, "totalNum", -1
//        );
        // 6.发送MQ消息
        UserCouponDTO userCouponDTO = new UserCouponDTO();
        userCouponDTO.setUserId(userId);
        userCouponDTO.setCouponId(couponId);
        mqHelper.send(MqConstants.Exchange.PROMOTION_EXCHANGE, MqConstants.Key.COUPON_RECEIVE, userCouponDTO);
//        }
    }

    private Coupon queryCouponByCache(Long couponId) {
        // 1.准备key
        String key = PromotionConstants.COUPON_CACHE_KEY_PREFIX + couponId;
        // 2.查询
        Map<Object, Object> objMap = redisTemplate.opsForHash().entries(key);
        if (objMap.isEmpty()) {
            return null;
        }
        // 3.数据反序列化
        return BeanUtils.mapToBean(objMap, Coupon.class, false, CopyOptions.create());
    }

    /**
     * TODO:兑换码兑换优惠券接口
     * @param code
     */
    @Override
//    @Lock(name = "lock:coupon:#{T(com.tianji.common.utils.UserContext).getUser()}")
    public void exchangeCoupon(String code) {
        // 1.校验并解析兑换码
        long serialNum = CodeUtil.parseCode(code);
//        // 2.校验是否已经兑换 SETBIT KEY 4 1 ，这里直接执行setbit，通过返回值来判断是否兑换过
//        boolean exchanged = exchangeCodeService.updateExchangeMark(serialNum, true);
//        if (exchanged) {
//            throw new BizIllegalException("兑换码已经被兑换过了");
//        }
//        try {
//            // 3.查询兑换码对应的优惠券id
////            ExchangeCode exchangeCode = exchangeCodeService.getById(serialNum);
//            Long couponId = exchangeCodeService.exchangeTargetId(serialNum);
//            if (couponId == null) {
//                throw new BizIllegalException("兑换码不存在！");
//            }
//            Coupon coupon = queryCouponByCache(couponId);
//            // 4.是否过期
//            LocalDateTime now = LocalDateTime.now();
////            if (now.isAfter(exchangeCode.getExpiredTime())) {
//            if (now.isAfter(coupon.getIssueEndTime())) {
//                throw new BizIllegalException("兑换码已经过期");
//            }
//            // 5.校验并生成用户券
//            // 5.1.查询优惠券
////            Coupon coupon = couponMapper.selectById(exchangeCode.getCouponId());
//            // 5.2.查询用户
//            Long userId = UserContext.getUser();
////            // 5.3.校验并生成用户券，更新兑换码状态
////            checkAndCreateUserCoupon(coupon, userId, serialNum);

        // 2.执行LUA脚本
        Long userId = UserContext.getUser();
        String execute = redisTemplate.execute(
                EXCHANGE_COUPON_SCRIPT,
                List.of(COUPON_CODE_MAP_KEY, COUPON_RANGE_KEY),
                String.valueOf(serialNum + 5000), userId.toString()
        );
        long result = NumberUtils.parseLong(execute);
        if (result < 10) {
            // 异常结果应该时在1~5之间
            throw new BizIllegalException(PromotionConstants.EXCHANGE_COUPON_ERROR_MSG[(int) (result - 1)]);
        }

        // 6.发送MQ消息通知
        UserCouponDTO userCouponDTO = new UserCouponDTO();
        userCouponDTO.setUserId(userId);
//        userCouponDTO.setCouponId(couponId);
        userCouponDTO.setCouponId(result);
        userCouponDTO.setSerialNum((int) serialNum);
        mqHelper.send(MqConstants.Exchange.PROMOTION_EXCHANGE, MqConstants.Key.COUPON_RECEIVE, userCouponDTO);
//    } catch(
//    Exception e)
//
//    {
//        // 重置兑换的标记 0
//        exchangeCodeService.updateExchangeMark(serialNum, false);
//        throw e;
//    }

    }

    /**
     * 由于事务方法需要 public 修饰，并且被 Spring 管理。因此要把事务方法向上抽取到 service 接口中
     * 该方法用于检查用户领取优惠券的资格并创建用户优惠券记录
     *
     * @param coupon    优惠券对象，包含优惠券的相关信息，如限领数量、库存等
     * @param userId    用户的 ID，用于标识领取优惠券的用户
     * @param serialNum 兑换码的序列号，如果使用兑换码领取优惠券，该参数不为 null
     */
    @MyLock(name = "lock:coupon")
    @Transactional
//    public void checkAndCreateUserCoupon(Coupon coupon, Long userId, Long serialNum) {
    public void checkAndCreateUserCoupon(UserCouponDTO userCouponDTO) {
        // 此处注释掉了同步代码块，原代码可能想通过用户 ID 加锁来保证并发安全
        // synchronized (userId.toString().intern()){
        // 1.校验每人限领数量
        // 1.1.统计当前用户对当前优惠券的已经领取的数量
        // 使用 MyBatis-Plus 的 LambdaQueryWrapper 来查询用户已领取指定优惠券的数量
//        Integer count = lambdaQuery()
//                .eq(UserCoupon::getUserId, userId)
//                .eq(UserCoupon::getCouponId, coupon.getId())
//                .count();
        Coupon coupon = couponMapper.selectById(userCouponDTO.getCouponId());
        if (null == coupon) {
            throw new BizIllegalException("优惠券不存在！");
        }
        // 1.2.校验限领数量
        // 如果已领取数量达到或超过优惠券的限领数量，抛出异常
//        if (count != null && count >= coupon.getUserLimit()) {
//            throw new BadRequestException("超出领取数量");
//        }
        // 2.更新优惠券的已经发放的数量 + 1
        // 调用优惠券映射器的方法来增加优惠券的已发放数量
        int r = couponMapper.incrIssueNum(coupon.getId());
        // 如果更新操作影响的行数为 0，说明优惠券库存不足
        if (r == 0) {
            throw new BizIllegalException("优惠券库存不足");
        }
        // 3.新增一个用户券
        // 调用保存用户优惠券的方法，将优惠券信息关联到用户
//        saveUserCoupon(coupon, userId);
        saveUserCoupon(coupon, userCouponDTO.getUserId());
        // 4.更新兑换码状态
        // 如果使用了兑换码领取优惠券，更新兑换码的状态为已使用，并关联到用户
//        if (serialNum != null) {
        if (userCouponDTO.getSerialNum() != null) {
            exchangeCodeService.lambdaUpdate()
//                    .set(ExchangeCode::getUserId, userId)
                    .set(ExchangeCode::getUserId, userCouponDTO.getUserId())
                    .set(ExchangeCode::getStatus, ExchangeCodeStatus.USED)
//                    .eq(ExchangeCode::getId, serialNum)
                    .eq(ExchangeCode::getId, userCouponDTO.getSerialNum())
                    .update();
        }
        // }
    }

    /**
     * 分页查询我的优惠券
     * @param userCouponQuery
     * @return
     */
    @Override
    public PageDTO<CouponVO> queryMyCouponPage(UserCouponQuery userCouponQuery) {
        Long userId = UserContext.getUser();
        Page<UserCoupon> page = lambdaQuery()
                .eq(UserCoupon::getUserId, userId)
                .eq(UserCoupon::getStatus, userCouponQuery.getStatus())
                .page(userCouponQuery.toMpPage(new OrderItem("term_end_time", true)));
        // 获取分页查询结果中的记录列表
        List<UserCoupon> records = page.getRecords();
        // 检查记录列表是否为空，如果为空则返回空的分页信息
        if (CollUtil.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        // 3.获取优惠券详细信息
        // 3.1.获取用户券关联的优惠券id
        // 使用 Java 8 Stream API 对用户优惠券记录列表进行处理
        // 提取每条记录中的优惠券 ID，并将其收集到一个 Set 集合中，以去重
        Set<Long> couponIds = records.stream().map(UserCoupon::getCouponId).collect(Collectors.toSet());
        // 3.2.查询
        // 根据收集到的优惠券 ID 集合，批量查询优惠券详细信息
        List<Coupon> coupons = couponMapper.selectBatchIds(couponIds);

        // 4.封装VO
        // 将分页信息和查询到的优惠券信息转换为视图对象，并封装到 PageDTO 中返回
        return PageDTO.of(page, BeanUtils.copyList(coupons, CouponVO.class));
    }


    /**
     * 保存用户优惠券信息到数据库
     *
     * @param coupon 优惠券对象，包含优惠券的基本信息，如有效期、优惠券 ID 等
     * @param userId 用户的 ID，用于关联优惠券与用户
     */
    private void saveUserCoupon(Coupon coupon, Long userId) {
        // 1. 初始化用户优惠券对象并设置基本信息
        // 创建一个新的用户优惠券对象
        UserCoupon userCoupon = new UserCoupon();
        // 设置用户优惠券关联的用户 ID
        userCoupon.setUserId(userId);
        // 设置用户优惠券关联的优惠券 ID
        userCoupon.setCouponId(coupon.getId());

        // 2. 处理并设置优惠券的有效期信息
        // 获取优惠券的开始有效期
        LocalDateTime termBeginTime = coupon.getTermBeginTime();
        // 获取优惠券的结束有效期
        LocalDateTime termEndTime = coupon.getTermEndTime();
        // 如果优惠券的开始有效期为空
        if (termBeginTime == null) {
            // 将当前时间设置为优惠券的开始有效期
            termBeginTime = LocalDateTime.now();
            // 根据优惠券的有效天数计算结束有效期
            termEndTime = termBeginTime.plusDays(coupon.getTermDays());
        }
        // 设置用户优惠券的开始有效期
        userCoupon.setTermBeginTime(termBeginTime);
        // 设置用户优惠券的结束有效期
        userCoupon.setTermEndTime(termEndTime);

        // 3. 保存用户优惠券信息到数据库
        // 调用服务层的保存方法，将用户优惠券对象保存到数据库
        this.save(userCoupon);
    }

}




